<script setup lang="ts">
import {Copy, Delete, Edit, List, SendAltFilled} from "@vicons/carbon";
import {computed, ref, watch, nextTick, onMounted} from "vue";
import {storeToRefs} from "pinia";
import {useLoadingBar, useMessage} from "naive-ui";


import {toByteArray} from "@/utils/byteParser"
import {useUserProfile} from "@/stores/useUserProfile.js"
import {sendToPort} from "@/utils/toSerial"
import {genDateTime} from '@/utils/getDateTime'
import {fuzzyMatch} from "@/utils/fuzzy"
import {renameDuplicates} from "@/utils/dataHandler"


const message = useMessage()
const loadingBar = useLoadingBar();
const props = defineProps({
	drag: {
		type: Boolean,
		required: true
	},
	// 指令在列表中的索引
	cmdIndex: {
		type: Number,
		required: true
	},
	// 单条指令对象
	cmdObj: {
		type: Object,
		required: true
	},
	// 指令配置(例如光学头...配置)
	commandOptions: {
		type: Array,
		required: false,
		default: []
	},
})
const loadingBarTargetRefs = ref([]); // 存储每个加载条的目标
const emit = defineEmits(["update:selected", "removeCmd", "loadingBar", "copyCmd"])

const {profile} = storeToRefs(useUserProfile())
const {telemetryConfig} = useUserProfile()
// 设置 ref
const setRef = (index) => (el) => {
	if (el) {
		loadingBarTargetRefs.value[index] = el;
	}
};


// 当前正在操作的指令
const currentModalItem = ref(null)

// 编辑指令的模态框
const showModal = ref(false)


// 选择指令时触发
const changeElement = (cmdItem, value, option) => {
	// 设置指令名称
	cmdItem.label = option.label
	// 编辑当前打开的指令参数
	let obj = cmdItem
	// 每条指令组件对应一个独立的参数体(深拷贝)
	obj.params = JSON.parse(JSON.stringify(props.commandOptions.find(item => item.label === obj.label).value))
	currentModalItem.value = cmdItem
}
const editCmd = (cmdItem) => {
	// 设置当前正在操作的指令对象
	currentModalItem.value = cmdItem
	// 判断当前是否选择了指令
	if (!cmdItem.label) {
		// 提示
		message.warning("未选择指令, 无法打开编辑窗口!")
		return
	}
	// 编辑按钮的回调
	showModal.value = true
}

function remove(index: number) {
	// 通知父组件删除
	emit("removeCmd", index)
}

const convertEnum = (obj) => {
	return Object.entries(obj).map(([key, value]) => ({label: `${value}(${key})`, value: parseInt(key)}))
}

function decimalToHex(decimal, type = 'uint', pad = 8) {
	if(decimal == undefined){
		return "Null"
	}
	if (type === 'float') {
		// 处理浮点数类型
		if (pad !== 4 && pad !== 8) {
			throw new Error('For float type, pad must be 4 or 8 bytes.');
		}
		const buffer = new ArrayBuffer(pad);
		const view = new DataView(buffer);
		if (pad === 4) {
			view.setFloat32(0, decimal, false); // 大端序
		} else {
			view.setFloat64(0, decimal, false);
		}
		const bytes = new Uint8Array(buffer);
		let hex = '';
		for (let i = 0; i < pad; i++) {
			hex += bytes[i].toString(16).padStart(2, '0');
		}
		return '0x' + hex.toUpperCase();
	} else {
		// 处理整数类型（uint/int）
		let decimalStr;
		if (typeof decimal === 'number') {
			// 处理科学计数法和浮点数
			decimalStr = decimal.toLocaleString('fullwide', {useGrouping: false});
		} else {
			decimalStr = decimal.toString();
		}
		// 提取整数部分
		let integerPartStr;
		if (decimalStr.includes('.')) {
			integerPartStr = decimalStr.split('.')[0];
		} else {
			integerPartStr = decimalStr;
		}
		// 处理科学计数法（如1e3）
		if (integerPartStr.includes('e') || integerPartStr.includes('E')) {
			integerPartStr = BigInt(Math.trunc(Number(decimalStr))).toString();
		}
		// 转换为BigInt
		let integer;
		try {
			integer = BigInt(integerPartStr);
		} catch (e) {
			throw new Error('Invalid decimal value: ' + decimal);
		}
		const bits = pad * 8;
		const maxValue = (1n << BigInt(bits)) - 1n;
		// 计算补码
		const hexValue = (integer & maxValue).toString(16).toUpperCase();
		// 填充前导零
		const paddedHex = hexValue.padStart(pad * 2, '0');
		return '0x' + paddedHex;
	}
}


// 校验和
const checksum = computed(() => {
	// 收集当前编辑的指令对象
	let currentObj = currentModalItem.value;
	if (currentObj) {
		// 切片出区间进行校验和计算
		let slicedParams = currentObj.params.slice(4, -2);

		// 转为字节数组并计算累加和
		const hexSum = calculateChecksum(toByteArray(slicedParams));
		// 刷新校验和的值
		currentObj.params.find(item => item.name === "校验和").default = `0x${hexSum}`
		return `0x${hexSum}`;
	}
});

// 参数收集并格式化展示
const paramsFormat = (params) => {
	if (!params) {
		return {};
	}
	return params.reduce((result, item) => {
		const {name, default: defaultValue, type, enum: enumValues, bit_length} = item;

		// 计算需要的十六进制字符数
		const hexLength = Math.ceil(bit_length / 4); // 每4位表示1个十六进制字符

		// 补零函数
		const formatHex = (hex) => {
			// 不处理undefined, null等值
			if (!hex && hex !== 0) return
			// 如果是负数，转换为无符号十六进制
			if (hex < 0) {
				// 假设需要处理为 32 位无符号数
				hex = (hex >>> 0).toString(16);
			} else {
				// 正常处理
				hex = hex.toString(16);
			}
			return `0x${hex.replace(/^0x/, "").padStart(hexLength, "0")}`;
		};

		if (enumValues && enumValues[defaultValue]) {
			// 如果有枚举值
			result[name] = `${enumValues[defaultValue]} (${formatHex(defaultValue)})`;
			// warning:暂时不支持浮点型
		} else if (type == "uint" || type == "int" || String(defaultValue)?.startsWith("0x")) {
			// 如果是数字类型，显示十进制(十六进制)
			const decimal = parseInt(defaultValue, 16); // 解析为十进制
			result[name] = `${decimal} (${formatHex(defaultValue)})`;
		} else {
			// 其他情况，直接格式化十六进制
			result[name] = formatHex(defaultValue);
		}

		return result;
	}, {});
};


// 计算校验和
function calculateChecksum(bytes) {
	// 计算字节和
	let sum = bytes.reduce((acc, byte) => acc + byte, 0);

	// 取低8位 (sum % 256)
	let checksum = sum % 256;
	// 返回校验和，转换成2位的16进制字符串
	return checksum.toString(16).padStart(2, '0').toUpperCase();
}

// 计算出源码
const sourceCodeHex = (params) => {
	if (!params) {
		return "";
	}
	console.log(params);

	// 拼接所有的默认值，并去掉0x前缀，同时处理负数的情况
	const hexString = params
		.map((item) => {
			let {default: defaultValue} = item;
			if (typeof defaultValue === "number") {
				// 如果是负数，将其转换为补码形式
				if (defaultValue < 0) {
					// 假设需要处理为一个 8 位无符号整数
					defaultValue = (defaultValue >>> 0).toString(16);
				} else {
					// dec转16
					defaultValue = defaultValue.toString(16)
				}
			}
			// 确保去掉 "0x" 并按长度补0
			return defaultValue ? defaultValue.replace(/^0x/, "").padStart(item.bit_length / 4, '0') : "";
		}).join(""); // 组合成连续字符串

	// 使用正则匹配每两个字符分割一次
	const formattedString = hexString.match(/.{1,2}/g)?.join(" ") || "";

	return formattedString;
};


const renderLabel = (option) => {
	// 获取param.default(param.enum[param.default])
	return option.label
}


// 发送指令
const sendCmd = async (cmdObj) => {
	// 这里做实际发送的操作(收集指令参数)(调用发送函数to_serial/to_socket)
	const {params} = cmdObj
	cmdObj.lastTime = genDateTime()
	loadingBar.start()
	// 获取当前串口状态
	const item = profile.value.tabList.find(item => item.label === cmdObj.deviceName)
	if (!item?.port?.writable) {
		message.error(`找不到可写流,请检查是否链接${cmdObj.deviceName}串口!`)
		loadingBar.error()
		return
	} else if (!params) {
		message.warning("请检查当前指令参数设置!")
		loadingBar.error()
		return
	}
	console.log(params, "指令参数")
	// 指令参数转字节
	let buffer = toByteArray(params)
	const writer = item.port.writable.getWriter({mode: "byob"}); // 获取新的 writer
	await sendToPort(writer, buffer)
	console.log("指令发送成功", buffer)
	loadingBar.finish()
}

const copyCmd = (index) => {
	emit("copyCmd", index)
}

// 计算当前 modal 的所有 default 值
const defaultValues = computed(() =>
	currentModalItem.value?.params?.map(item => item.default) ?? []
);

onMounted(() => {
	nextTick(() => {
		watch(defaultValues, (newValue, oldValue) => {
				// 当指令对象中任意属性发生变化触发校验和计算
				let currentObj = currentModalItem.value;
				if (currentObj && currentObj.params && currentObj.params.length > 0) {
					// 编辑参数
					let slicedParams = currentObj.params.slice(4, -2);

					// 转换为十六进制数组并计算累加和
					const hexSum = calculateChecksum(toByteArray(slicedParams));
					// 并把校验和赋值
					currentObj.params.find(item => item.name === "校验和").default = `0x${hexSum}`
				}
			},
			{
				deep: true
			}
		)
		// 传递进度条对象
		emit("loadingBar", {loadingBar: loadingBar, index: props.cmdIndex})
	})
})

// 模糊匹配
const fuzzyFilter = (patter, option) => {
	const result = fuzzyMatch(props.commandOptions, "label", patter);
	// 使用 some 方法检查是否有任何 remark  与 option.remark 匹配
	return result.some(item => item.label === option.label);
}

// 控制是否显示穿梭框
const sm = ref(false)
// 预处理界面
const showPreprocessing = ref(false)
// 参数界面
const paramEdit = ref(true)
const toggleSection = (type) => {
	showPreprocessing.value = (type === 'preprocessing');
	sm.value = (type === 'signal');
	paramEdit.value = (type === 'param');
}


// 穿梭框选项
const transferOptions = computed(() => {
	if (props.cmdObj.deviceName === "光学头") {
		return renameDuplicates(telemetryConfig.optical.telemetry.map(
			item => ({label: item.name, value: item.name})
		), "label")
	} else {
		return []
	}
})

// 选择单项质量
const selectSingleCmd = () => {
	// 给当前这条指令增加样式
	// emit("update:selected", !props.cmdObj.selected)
	props.cmdObj.selected = !props.cmdObj.selected
}
// 使用 ref 数组存储可修改的 params
const localParams = ref([]);

// 监听 props.cmdObj 变化
watchEffect(() => {
	if (props.cmdObj?.params) {
		localParams.value = props.cmdObj.params
			.map(p => {
				let {default: defaultValue} = p
				// 字符串类型整型转number
				if (typeof defaultValue === "string" && !defaultValue.startsWith("0x")) {
					defaultValue = Number(defaultValue)
				} else if (p.enum) {
					// 枚举型
					defaultValue = Number(defaultValue)
				}
				return {
					...p,
					default: defaultValue
				}
			});
		props.cmdObj.params = toRaw(localParams.value)
	}
});
</script>

<template>
	<div
		class="w-full bg-gray-500/10 px-2 rounded items-center justify-betweens cmdList"
		:class="cmdObj.selected ? 'bg-gray-900' : 'bg-gray-500/10'"
	>
		<!--指令主体内容-->
		<n-space :wrap="false" :wrap-item="false" align="center" class="h-full">
			<n-text class="me-2">{{ cmdIndex + 1 }}</n-text>
			<n-tag secondary
				   :type="cmdObj.type"
				   class="cursor-pointer active:bg-white"
				   @click="selectSingleCmd"
			>{{ cmdObj.deviceName }}
			</n-tag>
			<n-icon class="handle cursor-move text-2xl" :component="List"></n-icon>

			<n-select
				:filter="fuzzyFilter"
				class="w-full p-1"
				v-model:value="cmdObj.label"
				filterable
				placeholder="搜索/选择指令"
				:options="commandOptions&&commandOptions.length>0 ? commandOptions : []"
				:on-update:value="(value, option) => changeElement(cmdObj, value, option)"
			></n-select>
			<n-flex :wrap="false">

				<!--编辑指令按钮-->
				<n-button @click="editCmd(cmdObj)">
					<n-icon :component="Edit" class="text-xl"></n-icon>
					<n-text>编辑</n-text>
				</n-button>

				<!--发送指令按钮-->
				<n-button @click="sendCmd(cmdObj)">
					<n-icon :component="SendAltFilled" class="text-xl"
					></n-icon>
					<n-text>发送</n-text>
				</n-button>

				<!--快照按钮-->
				<!--				<n-button @click="snapshot(cmdIndex, cmdObj)" class="items-center">-->
				<!--					<n-icon :component="CameraFilled" class="text-xl"-->
				<!--					></n-icon>-->
				<!--					<n-text>快照</n-text>-->
				<!--				</n-button>-->

				<!--copy指令-->
				<n-button @click="copyCmd(cmdIndex)">
					<n-icon :component="Copy" class="text-xl"
					></n-icon>
				</n-button>
				<n-button @click="remove(cmdIndex)">
					<n-icon :component="Delete" class="text-red-500 text-xl"></n-icon>
				</n-button>
			</n-flex>
		</n-space>

		<!---可收缩下拉区域-->
		<n-collapse class="select-text cursor-text" display-directive="show">
			<!--源码和参数包显示-->
			<n-collapse-item name="sourceCode" :disabled="!cmdObj.params">
				<template #header>
					<n-text>显示源码</n-text>
				</template>
				<template #default>
					<n-scrollbar class="max-h-28">
						<n-code class="text-base font-bold">
							源码:{{ sourceCodeHex(cmdObj?.params).toUpperCase() }}
						</n-code>
						<n-divider class="pe-5 my-1"/>
						<n-code :code="JSON.stringify(paramsFormat(cmdObj?.params), null, 4)">
						</n-code>
					</n-scrollbar>
				</template>
				<template #header-extra>
					<n-text :depth="3">{{ cmdObj.lastTime || "" }}</n-text>
				</template>
			</n-collapse-item>

			<!--指令备注-->
			<n-collapse-item name="reamrk" v-if="cmdObj.remark">
				<template #header>
					<n-text tag="b" class="ms-1 text-red-300 text-base">
						<n-text>备注:</n-text>
						{{ cmdObj.remark }}
					</n-text>
				</template>
				<template #default>历史指令参数</template>
			</n-collapse-item>
		</n-collapse>

		<div
			:ref="setRef(cmdIndex)"
			class="h-0.5 xxx"
			id="xxx"
		/>

	</div>
	<!--指令编辑框-->
	<n-modal
		v-model:show="showModal"
		display-directive="show"
		class="h-fit w-11/12"
		preset="card"
		content-class="h-full w-full"
		footer-class="bg-zinc-800 flex p-2 gap-2 items-center"
		transform-origin="mouse"
	>
		<!--指令名称-->
		<template #header>
			<n-space align="center" :class="['w-full']">
				<n-tag :bordered="false" size="large" class="text-lg bg-[#3A4C7A]/75">{{ cmdObj?.label }}</n-tag>
				<n-divider vertical/>
				<n-text class="text-yellow-200/50">备注:</n-text>
				<n-input placeholder="指向角锥" class="w-fit" v-model:value.trim="cmdObj.remark"></n-input>
				<n-text>延时:</n-text>
				<n-input-number :min="0" v-model:value.number="cmdObj.delay" :default-value="0">
					<template #suffix>
						<n-text depth="3">秒(S)</n-text>
					</template>
				</n-input-number>

				<!--遥测关联-->
				<n-button type="info" @click="toggleSection('signal')" v-if="!sm">设置信号关联</n-button>
				<n-button type="warning" @click="toggleSection('param')" v-if="!paramEdit">回到参数编辑</n-button>
			</n-space>
		</template>
		<template #header-extra></template>
		<!--指令编辑窗口主要部分-->
		<template #default>
			<TransitionGroup name="slide-fade" mode="out-in">
				<!--帧头, bit_length, type, default, min, max, handler_func, enum-->
				<div v-if="paramEdit" class="h-full w-full flex flex-wrap">
					<!--过滤帧头,源地址,目的地址,指令码,帧尾部,只留下有效参数, 此处要考虑不同设备对于有效参数的限定-->
					<div
						v-for="(param, index) in localParams.filter(p => !props.cmdObj?.exclude_params.includes(p.name))"
						class="h-fit sm:w-1/2 lg:w-1/3 2xl:w-1/4 p-1 flex-initial">
						<!--参数标题-->
						<n-text strong>
							{{ index + 1 }}.{{ param.name }}
							(
							<n-text class="text-red-400">{{ param.bit_length }}位</n-text>
							)
						</n-text>

						<!--超过两个枚举值使用下拉框选择-->
						<n-select v-if="param.enum&&Object.keys(param.enum).length>2"
								  :consistent-menu-width="false"
								  :options="convertEnum(param.enum)"
								  v-model:value="param.default"
								  :render-label="renderLabel"
						>
						</n-select>

						<!--开关(只有两个枚举值时采用)-->
						<div class="bg-zinc-600/50 h-full p-1 flex items-center justify-center rounded-sm"
							 v-else-if="param.enum&&Object.keys(param.enum).length===2"
						>
							<n-switch
								size="large"
								:round="false"
								v-model:value="param.default"
								:checked-value="parseInt(Object.keys(param.enum)[0])"
								:unchecked-value="parseInt(Object.keys(param.enum)[1])"
							>
								<!--选中和非选中状态下的文字-->
								<template #checked>
									<n-text class="text-base">{{
											param.enum[Object.keys(param.enum)[0]]
										}}({{ Object.keys(param.enum)[0] }})
									</n-text>
								</template>
								<template #unchecked>
									<n-text class="text-base">{{
											param.enum[Object.keys(param.enum)[1]]
										}}({{ Object.keys(param.enum)[1] }})
									</n-text>
								</template>
							</n-switch>
						</div>

						<!-- 输入框-->
						<n-input-number v-else
										:status="param.status"
										:default-value="0"
										:update-value-on-input="true"
										v-model:value="param.default"
										clearable
										:max="param.max"
										:min="param.min"
						>
							<template #suffix>
								<n-text depth="3">
									HEX: {{ decimalToHex(param.default, param.type, param.bit_length / 8) }}
								</n-text>
							</template>
						</n-input-number>
					</div>
				</div>
				<div v-if="sm">
					<n-transfer
						v-model:value="cmdObj.relationshipField"
						:options="transferOptions"
						source-filterable
					/>
				</div>
			</TransitionGroup>

		</template>
		<template #footer>
			<!--用来展示无需修改的参数: 校验和 目的地址等...-->
			<div
				class="flex"
				v-for="item in cmdObj?.params && cmdObj?.params.filter(p => cmdObj?.exclude_params.includes(p.name))">
				<n-text tag="code" class="font-bold">
					{{ item.name }}:
					<n-text class="bg-pink-900 p-1  rounded">
						{{
							item.name === "校验和"
								? checksum && checksum.toUpperCase()
								: (typeof item.default === "number"
									? `0x${item.default.toString(16).padStart(item.bit_length / 4, '0').toUpperCase()}`
									: item.default?.toString().startsWith("0x")
										? item.default.toUpperCase()
										: `0x${parseInt(item.default, 10).toString(16).padStart(item.bit_length / 4, '0').toUpperCase()}`)
						}}

					</n-text>
				</n-text>
			</div>
			<n-button class="ms-auto" type="info" @click="() => {sendCmd(cmdObj); showModal=false}">发送并退出
			</n-button>
		</template>
	</n-modal>
</template>

<style>
.fade-move,
.fade-enter-active,
.fade-leave-active {
	transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

.fade-enter-from,
.fade-leave-to {
	opacity: 0;
	transform: scaleY(0.01) translate(30px, 0);
}

.fade-leave-active {
	position: absolute;
}

.sort-target {
	padding: 0 1rem;
}

.slide-fade-enter-active {
	transition: all 0.5s ease-out;
}

.slide-fade-leave-active {
	transition: all 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
	transform: translateX(20px);
	opacity: 0;
}

/**/
.bounce-enter-active {
	animation: bounce-in 0.5s;
}

.bounce-leave-active {
	animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
	0% {
		transform: scale(0);
	}
	50% {
		transform: scale(1.25);
	}
	100% {
		transform: scale(1);
	}
}
</style>